"""

.. module:: runtime
.. moduleauthor:: @author: 

The purpose of this module is to control the node startup and runtime.
   
.. seealso:: 

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
from Queue import Queue
import time
import logging
import os
import urlparse
import sys
import uuid
import datetime
if sys.platform is 'win32':
    import win32serviceutil
    import win32service
    import win32event 
    import winerror
elif sys.platform is 'darwin': # OS/X
    pass
else: # assume Linux
    pass


from node import Console
from datastore.database import *
from datastore.resource import *
from datastore.uri import *
from runtime import build_version

# Constants
#
SERVICE_NAME_PREFIX = "HAF eDA Node"
SERVICE_DISPLAYNAME_PREFIX = "HAF eDA Node"
SERVICE_DESCRIPTION = "HAF eDA Node for doing blah"

CONFIGSTATE_INITIALISED_STAGE1 = "1"
CONFIGSTATE_INITIALISED_STAGE2 = "2"

# Classes
#

class StartupException(Exception):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
    """
    
    # Class constants
    #
    def __init__(self):
        # <purpose>
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        pass

if sys.platform is 'win32':
    class NodeService(win32serviceutil.ServiceFramework):
        """The purpose of this class is to interface with Windows Services.
    
        *Usage examples*::
    
            example code
    
        *Common Arguments for Class methods*:
            <arguments>
        
        *Audience*:
            <audience>
        
        *Constants*:
            .. data:: constant_1
            
                constant_1 is used for ......
        """
        
        _svc_name_ = "NodeService"
        _svc_display_name_ = SERVICE_DISPLAYNAME_PREFIX
        _svc_description_ = SERVICE_DESCRIPTION
    
        def __init__(self, args):
            # Initialise the NodeService class.
            #
            # Parameters:
            #     <param 1> :
            #     ...
            #
            
            # Note: After the service is removed, the DLL containing the message 
            # definitions is unloaded and a more verbose message is seen in the Windows
            # Event Log.
            #
            self.logger = logging.getLogger('yarra.runtime.rt.NodeService')
            self.logger.log(logging.DEBUG, "")
            
            try:
                win32serviceutil.ServiceFramework.__init__(self, [NodeService._svc_name_])
                
                # Create an event which we will use to wait on. The "service stop" request will set this event.
                #
                self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
            except Exception as e:
                self.logger.exception("%s" % e)
            
        def SvcStop(self):
            """This function is called by Windows Service to stop the node service. 
            
            :rtype: None
            """
            
            self.logger.log(logging.DEBUG, "")
            
            # Before we do anything, tell the SCM we are starting the stop process.
            #
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            
            # And set my event.
            #
            win32event.SetEvent(self.hWaitStop)
    
        def SvcDoRun(self):
            """This function is called by Windows Service to run the node service. 
            
            :rtype: None
            """
            
            self.logger.log(logging.INFO, "")
            
            rt = Runtime()
            rt.start()
            
            self.timeout=60000
            while (True):
                # Wait for service stop signal, if I timeout, loop again
                #
                rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
                
                self.logger.log(logging.DEBUG, 
                    "Checking for STOP from Windows Service...")
                
                # Check to see if self.hWaitStop happened
                #
                if (rc == win32event.WAIT_OBJECT_0):
                    # Stop signal encountered
                    #
                    self.logger.log(logging.DEBUG, 
                        "STOP event received by NodeService")
                    
                    rt.spaces_context.put(
                            Uri('internal:base/runtime/node/shutdown/quit'), 
                            Resource(type='term:base/runtime/node_control'), 
                            'cb_node_shutdown')
                    break
            
            rt.post_start()
            
    
        @staticmethod
        def InstallService(distribution):
            """This function installs the node with Windows Service. 
            
            :param distribution: Distribution - distribution configuration.
            :rtype: None
            """
            
            # Note: The current working directory (CWD) for Windows Services is:
            #
            #       - "C:\Windows\system32"
            #
            #       so if we look for the datastore in the CWD we won't find it. So, on
            #       startup, we infer the path to the datastore from the path of the 
            #       executable.
            
            try:
                win32serviceutil.InstallService(pythonClassString=win32serviceutil.GetServiceClassString(NodeService), 
                                                serviceName=NodeService._svc_name_, 
                                                displayName=NodeService._svc_display_name_, 
                                                startType=win32service.SERVICE_AUTO_START, 
    #                                            startType=win32service.SERVICE_DEMAND_START, 
                                                exeName=distribution.exe_pathname, 
                                                exeArgs=distribution.database_filename,
                                                description=NodeService._svc_description_)
                
                logging.log(logging.INFO, "Service (%s) installed." % NodeService._svc_name_)
            except win32service.error, (hr, fn, msg) :
                if (hr == winerror.ERROR_SERVICE_EXISTS):
                    # Service already exists. Update it.
                    #
                    try:
                        logging.log(logging.INFO, "Service (%s) already installed. Updating." % NodeService._svc_name_)
                        
                        win32serviceutil.ChangeServiceConfig(pythonClassString=win32serviceutil.GetServiceClassString(NodeService), 
                                                             serviceName=NodeService._svc_name_, 
                                                             startType=win32service.SERVICE_AUTO_START, 
    #                                                         startType=win32service.SERVICE_DEMAND_START, 
                                                             exeName=distribution.exe_pathname,
                                                             exeArgs=distribution.database_filename,
                                                             displayName=NodeService._svc_display_name_, 
                                                             description=NodeService._svc_description_)
    
                        logging.log(logging.INFO, "Service (%s) updated." % NodeService._svc_name_)
                    except win32service.error, (hr, fn, msg):
                        logging.exception("Error changing service (%s) configuration: %s (%d)" % (NodeService._svc_name_, msg, hr))
                        raise NodeServiceException(msg)
                else:
                    logging.exception("Error installing service (%s): %s (%d)" % (NodeService._svc_name_, msg, hr))
                    raise NodeServiceException(msg)
        
        @staticmethod
        def RemoveService():
            """This function removes the node from Windows Service. 
            
            :rtype: None
            """
            
            try:
                win32serviceutil.RemoveService(serviceName=NodeService._svc_name_)
                logging.log(logging.INFO, "Service (%s) removed." % NodeService._svc_name_)
            except win32service.error, (hr, fn, msg) :
                logging.exception("Error removing service (%s): %s (%d)" % (NodeService._svc_name_, msg, hr))
                raise NodeServiceException(msg)
elif sys.platform is 'darwin': # OS/X
    pass
else: # assume Linux
    pass


class NodeServiceException(Exception):
    """This class represents an exception during Windows Service installation
    or running.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
    """
    
    def __init__(self, msg):
        self.msg = msg
    
    def __str__(self):
        return self.msg


class Runtime(object):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
        
    distribution = None
    
    def __init__(self):
        # <purpose>
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        # list of all the tasks started by runtime
        #
        self.tasks = []
        self.spaces_context = None
        self.ds = None
        
        self.logger = logging.getLogger('yarra.runtime.rt.Runtime')
    
    def get_class_type(self, module_path, class_name):
        """This function returns the type of the class. 
        
        :param module_path: <class> or <type>
        :param class_name: <class> or <type>
        :rtype: <return type>
        """
        
        # import the module
        import_string = 'from {0} import *'.format(module_path)
        self.logger.log(logging.DEBUG, "executing import: {0} ".format(import_string))
        exec import_string
        # get reference to the module
        module = sys.modules[module_path]
        # get a reference to the class
        targetClass = getattr(module, class_name)
        return targetClass
    
#    def initialise_spaces(self, config_file_rs_list=[], site_file_rs_list=[], template_file_rs_list=[]):
#        """This function initialises spaces from configuration.
#        
#        The method initialises spaces with:
#        - all the uri namespaces bindings in the argument configuration files
#        - the configuration files. 
#        - the site configurations.
#        
#        :param config_file_rs_list: List - list of resource config files.
#        :param site_file_rs_list: List - list of resource site files.
#        :param template_file_rs_list: List - list of resource HTML template files.
#        """
#        
#        from datastore.resource import Resource
#        package = Uri('package:')
#        
#        # load all the bindings first
#        #
#        for config_file_rs in config_file_rs_list:
#            bindings = config_file_rs.get_var(package.manifest.binding)
#            self.spaces_context.load_bindings(bindings)
#        
#        config_uri = self.spaces_context.uri('localhost:node-configuration')
#                
#        # now initialise spaces with other configuration data using the bindings 
#        # loaded in the code above to make it easier to navigate the configuration
#        # files.
#        for config_file_rs in config_file_rs_list:
#            # add the configuration file to spaces
#            package_name = config_file_rs.get_single_metadata(package.packagename)
#            project_name = config_file_rs.get_single_metadata(package.projectname)
#            config_uri = config_uri.__getattr__(project_name).__getattr__(package_name)
#            self.logger.log(logging.DEBUG, "loading configuration.xml (%s) into spaces" % config_uri)
#            self.spaces_context.put(config_uri, config_file_rs)
#            
#            # add the spaces defs to spaces
#            #
#            spaces_defs = config_file_rs.get_var(package.manifest.spaces)
#            for spaces_def_key in spaces_defs.keys():
#                spaces_def = spaces_defs[spaces_def_key]
#                new_spaces_def_rs = Resource()
#                new_spaces_def_rs.set_var(package.manifest.spaces, spaces_def)
#                spaces_def_uri = self.spaces_context.uri(spaces_def_key)
#                self.logger.log(logging.DEBUG, "loading definition (%s) into spaces" % spaces_def_uri)
#                self.spaces_context.put(spaces_def_uri, new_spaces_def_rs)
#        
#        # load site definitions into spaces...
#        #
#        for site_file_rs in site_file_rs_list:
#            site_file_uri = site_file_rs.get_single_metadata(package.siteuri)
#            self.logger.log(logging.DEBUG, "loading site (%s) into spaces" % site_file_uri)
#            self.spaces_context.put(site_file_uri, site_file_rs)
#        
#        # load HTML templates into spaces...
#        #
#        for template_file_rs in template_file_rs_list:
#            template_file_uri = template_file_rs.get_single_metadata(package.datauri)
#            self.logger.log(logging.DEBUG, "loading HTML template (%s) into spaces" % template_file_uri)
#            self.spaces_context.put(template_file_uri, template_file_rs)
        
    def create_task(self, ds, spaces, task_fully_qualified_path, task_uri, task_def_rs):
        """This function returns an instance of the task specified by the qualified path and task definition. 
        
        :param ds: datastore.database.DataStore
        :param spaces: runtime.spaces.Spaces
        :param task_fully_qualified_path: <class> or <type>
        :param task_uri: <class> or <type>
        :param task_def_rs: <class> or <type>
        :rtype: runtime.task.Task
        """
        
        self.logger.log(logging.DEBUG, "task_fully_qualified_path (%s), task_uri (%s)" % (task_fully_qualified_path, task_uri))
        
        from runtime.task import Task, NonTaskException
             
        module_path, task_class_name = task_fully_qualified_path.strip().rsplit('.', 1)
        target_class = self.get_class_type(module_path, task_class_name)
        
        # make sure the class is a task or a subclass of a task
        #
        if not (Task is target_class or Task in target_class.__bases__):
            raise NonTaskException()
        else:
            # instantiate the class
            #
            return target_class(ds, spaces, task_uri, task_def_rs)
    
    
    def post_start(self):
        """This function handles post start events.
        
        Handles events after the node has successfully started. This includes shutting down of the node. 

        :rtype: None
        """
        
        self.logger.log(logging.DEBUG, "Post start events")
        
        while True:
            (callback, method, uri, return_value, result, self.spaces_context, kwargs) = self.spaces_context.queue.get(True)
            
            self.logger.log(logging.DEBUG, "Received queued message, callback (%s), method (%s), uri (%s)" % (callback, method, uri))
            
            # very simple right now. Checks the callback is the shutdown one. Otherwise ignores the callback
            if callback == 'cb_node_shutdown' and method == 'put':
                self.logger.log(logging.DEBUG, "Received cb_node_shutdown call.")
                
                from datastore.resource import Resource
                # return some confirmation to the user.
                # TODO: NOTE: This is misleading as the shutdown hasn't even started. We can actually only shutdown the 
                # tasks after we return the message. Otherwise if we try to shutdown all the tasks first, the http server
                # will be destroyed and we can't return anything to the user
                rs = Resource(type='term:base/runtime/node_control')
                self.spaces_context.return_from_callback(self.spaces_context, rs)
                return self.cb_node_shutdown(uri)
            
    def cb_node_shutdown(self, uri):
        """
        This function shuts down the node.
        Shuts down the node currently by stopping all the tasks. 
        Returns the exit code to specify a shutdown or a restart. 
        
        :param uri: datastore.uri.Uri - uri called to shutdown
        :rtype: int
        """
        
        self.logger.log(logging.INFO, "Processing cb_node_shutdown call.")
        
        # set exit code to shutdown or restart base on the last part of the Uri
        if uri.split2()[-1] == 'quit':
            # shutdown
            exit_code = 0
        else:
            # restart
            exit_code = 1
        self.stop_tasks(Uri('internal:task/'))
        
        # don't exit until all the tasks have shutdown
        for task in self.tasks:
            while task.isAlive():
                # if a task is still alive, wait and check again
                time.sleep(1)
        
        return exit_code
    
    def stop_tasks(self, uri):
        """This function stops tasks.
        
        Stops the tasks registered under the uri. It does this by checking if the resource at that uri is a 
        task definition resource. If it is, then it will attempt to stop the task by deleting the resource at the
        uri. 
        
        :param uri: datastore.uri.Uri - uri of where all the tasks are located
        :rtype: None
        """
        
        self.logger.log(logging.INFO, "")
        
        task_uri_list = self.spaces_context.find_all(uri)
        if task_uri_list:
            for task_uri in task_uri_list:
                task_rs = self.spaces_context.get(task_uri)
                if task_rs and task_rs.get_type() == Uri('term:base/runtime/task'):
                    # TODO: How do we stop a task? Delete is being used in HTTPTask to delete as site.
                    self.spaces_context.delete(task_uri)
                else:
                    self.stop_tasks(task_uri)
    
    def install(self, configurables, distribution):
        """This function the locks the datastore, generates the administration
        password, creates the Root node of the datastore, and writes all 
        configurables to the datastore, including the admin password. 
        
        :param configurables: Configurables - user configuration items.
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "Updating database %s" % \
                        distribution.database_pathname)
        
        # open and lock the database.
        #
        ds = DataStore(os.path.join(distribution.database_pathname, 
                                    distribution.database_filename))
        ds.lock()
        
        # generate administration password. We use a random UUID.
        # Display this to administration user.
        configurables.adminpassword = str(uuid.uuid4())[0:8]
        Console.display("Administration password is (%s)" % \
                        configurables.adminpassword)
        
        dt = datetime.datetime.now()
        
        # update the Root node and write to datastore...
        resources = ds.find_type(Uri('term:base/datastore/root'))
        if len(resources) == 1:
            rs = resources[0]
            self.logger.log(logging.INFO, 
                "Datastore build %s generated at %s" % \
                (rs.datastore_revision, rs.datastore_timestamp))
            rs.node_id = distribution.node_ID
            rs.init_revision = build_version.BUILD_REVISION
            rs.init_timestamp = dt.strftime("%Y-%m-%dT%H:%M:%S")
            ds.put(rs)
            self.logger.log(logging.DEBUG, rs)
        else:
            self.logger.log(logging.ERROR, 
                "Fatal Error: Expecting a single root resource. %i found." % \
                len(resources))
            sys.exit(1)

        # create the configuration resource (copy data from the configurables
        # dict.
        rs = Resource(type='term:base/runtime/node_configuration')
        rs.set_metadata(Uri('term:base/build/autoload_suffix'), 
                        'node_configuration')
        for key in configurables.__dict__.keys():
            rs.set_var(key, configurables.__dict__[key])
        rs.state =  CONFIGSTATE_INITIALISED_STAGE1
        ds.put(rs)
        self.logger.log(logging.DEBUG, rs)
        sys.exit()
        
    
    def start(self):
        """This function is the mainline of Runtime regardless of whether we're
        started as a service or from a shell.
        """
        
        from datastore.database import DataStore
        from runtime.spaces import Spaces, SpacesContext
        
        self.logger.log(logging.INFO, "")

        queue = Queue()
        spaces = Spaces()
        self.spaces_context = SpacesContext(spaces, queue)
        
        # open and lock the database.
        self.ds = DataStore(
            os.path.join(Runtime.distribution.database_pathname,
                         Runtime.distribution.database_filename))
        self.ds.lock()
        
        # load and process the configuration files.
        config_list = self.ds.find_type(Uri('term:base/runtime/configuration'))
        for config in config_list:
            self.logger.log(logging.INFO, 
                "Processing configuration %s" % config.get_single_metadata(Uri('dc:title')))
#            config_file = config.get_metadata_single(Uri('term:base/build/file'))
#            config_module  = config.get_metadata_single(Uri('term:base/build/module'))
#            config_package = config.get_metadata_single(Uri('term:base/build/package'))
#            config_project = config.get_metadata_single(Uri('term:base/build/project'))
            
            # process bindings
            if config.has_var('bindings'):
                bindings_list = config.bindings
            else:
                bindings_list = {}
            for key in bindings_list.keys():
                self.logger.log(logging.DEBUG, 
                    "  binding %s=%s" % (key, bindings_list[key]['bind']))
                Uri.add_namespace(key, bindings_list[key]['bind'])

            # process definitions
            if config.has_var('definitions'):
                definitions_list = config.definitions
            else:
                definitions_list = {}
            for key in definitions_list.keys():
                definition_rs = Resource(type='term:base/runtime/definition')
                if 'dc:title' in definitions_list[key].keys():
                    definition_rs.set_metadata(Uri('dc:title'), 
                        definitions_list[key]['dc:title'])
                if 'dc:description' in definitions_list[key].keys():
                    definition_rs.set_metadata(Uri('dc:description'), 
                        definitions_list[key]['dc:description'])
                    self.logger.log(logging.DEBUG, "  definition %s" % (key))
                if 'autoload_prefix' in definitions_list[key].keys():
                    definition_rs.autoload_prefix = definitions_list[key]['autoload_prefix']
                # dublin core elements: reference, termtype, version, note
                if 'reference' in definitions_list[key].keys():
                    definition_rs.reference = definitions_list[key]['reference']
                if 'termtype' in definitions_list[key].keys():
                    definition_rs.termtype = definitions_list[key]['termtype']
                if 'version' in definitions_list[key].keys():
                    definition_rs.version = definitions_list[key]['version']
                if 'note' in definitions_list[key].keys():
                    definition_rs.note = definitions_list[key]['note']
                if 'metadata' in definitions_list[key].keys():
                    for metadata in definitions_list[key]['metadata'].keys():
                        self.logger.log(logging.DEBUG, 
                            "    metadata %s" % (metadata))
                    definition_rs.metadata = definitions_list[key]['metadata']
                if 'content' in definitions_list[key].keys():
                    for content in definitions_list[key]['content'].keys():
                        self.logger.log(logging.DEBUG, 
                            "    content %s" % (content))
                    definition_rs.content = definitions_list[key]['content']
                self.spaces_context.put(Uri(key), definition_rs)

            # process tasks (just create the task definition resources)
            if config.has_var('tasks'):
                tasks_list = config.tasks
            else:
                tasks_list = {}
            for key in tasks_list.keys():
                self.logger.log(logging.DEBUG, 
                    "  task %s" % (tasks_list[key]['dc:title']))
                # create a new resource that contains the task definition for a task
                # and put it to the task's uri
                task_def_rs = Resource(type='term:base/runtime/task')
                if 'dc:title' in tasks_list[key].keys():
                    task_def_rs.set_metadata(Uri('dc:title'), 
                        tasks_list[key]['dc:title'])
                if 'dc:description' in tasks_list[key].keys():
                    task_def_rs.set_metadata(Uri('dc:description'), 
                        tasks_list[key]['dc:description'])
                if 'classname' in tasks_list[key].keys():
                    task_def_rs.classname = tasks_list[key]['classname']
                if 'options' in tasks_list[key].keys():
                    task_def_rs.options = tasks_list[key]['options']
                self.spaces_context.put(Uri(key), task_def_rs)
                #self.logger.log(logging.INFO, 
                #    "    definition_rs %s" % (task_def_rs))

        # process resources to autoload from the datastore into spaces.
        # definition_rs.set_type(Uri('term:base/runtime/definition'))
        suffix_uri = Uri('term:base/build/autoload_suffix')
        for dd_uri in self.spaces_context.find_all(Uri('term:')):
            dd_rs = self.spaces_context.get(dd_uri)
            if dd_rs.has_var('autoload_prefix'):
                self.logger.log(logging.DEBUG, 
                    "autoload %s=%s" % (dd_uri, dd_rs.autoload_prefix))
                try:
                    autoload_resource_list = self.ds.find_type(dd_uri)
                except:
                    autoload_resource_list = {}
                for r in autoload_resource_list:
                    if r.metadata_has_key(suffix_uri):
                        uri = dd_rs.autoload_prefix + r.get_single_metadata(suffix_uri)
                        self.spaces_context.put(uri, r)
                        self.logger.log(logging.DEBUG, 
                            "autoload loaded %s" % uri)
            #else:
            #    self.logger.log(logging.INFO, 
            #        "no autoload %s" % (dd_uri))
                
        # setup each task (using the task definition resources)
        tasks = self.spaces_context.find_all(Uri('internal:task/'))
        for task_uri in tasks:
            task_rs = self.spaces_context.get(task_uri)
            self.logger.log(logging.INFO,
                "starting task %s" % \
                task_rs.get_single_metadata(Uri('dc:title')))
            task = self.create_task(
                self.ds, 
                self.spaces_context.spaces, 
                task_rs.classname, 
                task_uri, 
                task_rs)            
            self.tasks.append(task)
        
        # Watch for quit Uri on Spaces. This is our shutdown event.
        self.spaces_context.watch(['put'], 
                Uri('internal:base/runtime/node/shutdown/quit'), 
                'cb_node_shutdown')
        
#        # update packages
#        is_updated = self.update_packages()
#        if (is_updated):
#            self.logger.log(logging.INFO, 'Restart')
#            sys.exit(1)
        
        
        # start all the tasks
        for task in self.tasks:
            task.start()
        
        self.logger.log(logging.INFO, 'All tasks have started')
    
    def startProcess(self, distribution):
        """This function is starts Runtime for a command-line start.
        
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "")
        
        Runtime.distribution = distribution
        self.start()
        
        # TODO: Tie this shutdown request to Ctrl-C
        #
#        rt.spaces_context.process(Uri('http://blah/quit'), get='cb_node_shutdown')

        self.post_start()
    
    def startAsService(self, distribution):
        """This function starts Runtime as a Service.
        
        :param distribution: Distribution - distribution configuration.
        """
        
        self.logger.log(logging.INFO, "")
        
        Runtime.distribution = distribution
        
        # We have been started from Windows Services...
        #
        import servicemanager
        servicemanager.PrepareToHostSingle(NodeService)
    
        try:
            servicemanager.StartServiceCtrlDispatcher()
        except:
            self.logger.exception("Unable to start as service: %s" % sys.exc_info()[1])
    
    def installService(self, distribution):
        """This function ... 
        
        :param distribution: Distribution
        :param param_2: <class> or <type>
        :rtype: <return type>
        """
        
        self.logger.log(logging.INFO, "")
        
        try:
            NodeService.InstallService(distribution)
            self.logger.log(logging.INFO, "Service installed.")
        except Exception as e:
            self.logger.log(logging.ERROR, "Error installing service: %s" % e)
    
    def removeService(self):
        """This function ... 
        
        :param param_1: <class> or <type>
        :param param_2: <class> or <type>
        :rtype: <return type>
        """
        
        self.logger.log(logging.INFO, "")
        
        try:
            NodeService.RemoveService()
            self.logger.log(logging.INFO, "Service removed.")
        except Exception as e:
            self.logger.log(logging.ERROR, "Error removing service: %s" % e)